var dgram = require('dgram');
var http = require('http');
var qs = require('querystring');
var util = require('util');
var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var Device = new Schema({
	hwAddress : {
		type : String,
		unique : true
	},
	inetAddress : String,
	name : {
		type : String,
		unique : true
	},
	status : {
		type : String,
		index : true,
		enum : [ 'online', 'offline', 'noconf' ]
	},
	location : String,
	description : String,
	ports : [ {
		type : Schema.ObjectId,
		ref : 'Port'
	} ]
});

Device.pre('remove', function(next) {
	var self = this;
	model.Ports.remove({
		device : self._id
	}, next);
});

Device.statics.locate = function() {
	var msg = new Buffer("Discovery: Who is out there?\r\n");
	model.Device.remove({
		status : 'noconf'
	}, function(e) {
		log.debug("Removed unconfugured devices");
		model.Device.update({
			status : 'online'
		}, {
			status : 'offline'
		}, {
			multi : true
		}, function(e) {
			if (e)
				log.error(e, "Models/Device2:56");
			log.debug("Set all to 'Offline'");
			model.Device.locatorSocket.send(msg, 0, msg.length, 30303,
					'255.255.255.255', function(e) {
						if (e)
							log.error(e, 'Models/Device2:47');
			});
		});
	});
};

// Poll all devices
Device.statics.fetch = function() {
	try {
		log.debug('Update All');
		this.find({
			status : 'online'
		}, function(e, devices) {
			devices.forEach(function(device) {
				device.fetch();
			});
		});
	} catch (e) {
		if (e)
			log.error(e, 'Models/Device2:77');
	}
};

// Poll Input Ports
Device.methods.fetch = function() {
	log.debug('Fetch Data: ' + this.name);
	if (this.status != 'online')
		return;

	var self = this;
	var data = '';

	try {
		var request = http.request({
			host : self.inetAddress,
			port : 80,
			method : 'GET',
			path : '/status.htm'
		}, function(response) {
			response.setEncoding('utf8');

			var updatePort = function(name, value) {
				log.debug(name + ': ' + value);
				model.Port.findOne({
					name : name,
					device : self._id,
					direction : 'Input'
				}, function(e, port) {
					if (port) {
						if (port.value !== value) {
							log.debug("Updating Port " + port.name + '. '
									+ name + ' = ' + value);
							switch (port.type) {
							case 'Boolean':
								port.value = value;
								break;
							case 'Number':
								port.value = value * 1;
								break;
							}
							port
									.save(function(e) {
										if (e)
											log.error(e, 'Models/Device2:112');
										tasks.emit('port_update', port._id,
												port.value);
									});
						}
					}
				});
			};

			response.on('data', function(chunk) {
				data += chunk;
			});

			response.on('end', function() {
				try {
					var rcvdPorts = JSON.parse(data);

					for ( var p in rcvdPorts) {
						updatePort(p, rcvdPorts[p]);
					}
				} catch (e) {
					if (e)
						log.error(e, 'Models/Device2:134 -- ' + self.name);
				}
			});
		});

		request.on('error', function(e) {
			log.error(e, "Models/Device2:140 -- " + self.name);
		});

		request.end();
	} catch (e) {
		log.error(e, 'Models/Device2:145');
	}
};

Device.statics.put = function() {
	model.Device.find({
		status : 'online'
	}).populate('ports').run(function(e, devices) {
		devices.forEach(function(device) {
			device.put();
		});
	});
};

Device.methods.put = function() {
	var self = this;

	if (this.status != 'online')
		return;

	var ports = {};
	this.ports.forEach(function(port) {
		if (port.direction == 'Output') {
			ports[port.name] = port.value;
		}
	});

	var path = '/forms.htm?' + qs.stringify(ports);

	try {
		var request = http.request({
			host : self.inetAddress,
			port : 80,
			method : 'GET',
			path : path
		}, function(response) {
			log.debug("Sending Port Update to " + self.name);
		});

		request.on('error', function(e) {
			log.error(e, "Models/Device2:185 -- " + self.name);
		});

		request.end();
	} catch (e) {
		log.error(e, 'Models/Device2:190');
	}
};

model.Device = mongoose.model('Device', Device);

model.Device.update({}, {
	status : 'offline'
}, {
	multi : true
}, function(e) {
	if (e)
		log.error(e, 'Models/Device2:202');
	log.debug("Setting all to 'Offline'");
});

// Start UDP listener
try {
	model.Device.locatorSocket = dgram.createSocket("udp4");
	model.Device.locatorSocket.bind(30303, '172.16.254.254');
	model.Device.locatorSocket.setBroadcast(true);

	model.Device.locatorSocket.on('message', function(message, rinfo) {
		var messageFields = message.toString().match(
				/(.*?)\s+\r\n([0-9a-fA-f\-]{17})/);

		var hwAddress = messageFields[2].toLowerCase();
		var name = messageFields[1].toLowerCase();

		model.Device.findOne({
			hwAddress : hwAddress
		}, function(e, device) {
			if (device) {
				if (device.status != 'noconf')
					device.status = 'online';
				device.inetAddress = rinfo.address;
				device.save(function(e) {
					if(e)
						log.error(e, "Models/Device2:236");
					log.debug("Set " + device.name + " to 'Online'");
				});
			} else {
				var n = new model.Device();
				n.hwAddress = hwAddress;
				n.inetAddress = rinfo.address;
				n.name = name;
				n.status = 'noconf';
				n.location = '';
				n.description = '';

				n.save(function(e) {
					if (e)
						log.error(e, 'Models/Device2:239');
				});
			}
		});
	});
} catch (e) {
	log.error(e, 'Models/Device2:245');
}

// Periodic tasks
tasks.on('device_locate_interval', function() {
	try {
		model.Device.locate();
	} catch (e) {
		log.error(e, 'Models/Device2:253');
		process.exit(1);
	}
});

tasks.on('device_fetch_interval', function() {
	try {
		model.Device.fetch();
	} catch (e) {
		log.error(e, 'Models/Device2:262');
		process.exit(1);
	}
});

tasks.on('device_put_interval', function() {
	try {
		model.Device.put();
	} catch (e) {
		log.error(e, 'Models/Device2:271');
		process.exit(1);
	}
});

tasks.addEmitter('device_locate_interval', 997);
tasks.addEmitter('device_fetch_interval', 511);
tasks.addEmitter('device_put_interval', 1013);
